﻿/* Copyright 2010 Igor Francé
 * 
 * This file is part of ECMADoc (http://ecmadoc.codeplex.com).
 * 
 * ECMADoc is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * ECMADoc is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with ECMADoc. 
 * If not, see http://www.gnu.org/licenses/.
 */
namespace EcmaDoc.Core.Items
{
	using System;
	using System.Linq;
	using System.Collections.Generic;
	using System.Text.RegularExpressions;
	using System.Xml;

	using Configuration;

	/// <summary>
	/// Represents a project code item.
	/// </summary>
	public class ProjectItem : CodeItem
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ProjectItem"/> class, using the specified <paramref name="projectInfo"/>.
		/// </summary>
		/// <param name="projectInfo">The project configuration to use with this project item.</param>
		public ProjectItem(ProjectInfo projectInfo)
			: this(projectInfo, null)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ProjectItem"/> class, using the specified <paramref name="projectInfo"/>
		/// and <paramref name="comment"/>.
		/// </summary>
		/// <param name="projectInfo">The project configuration to use with this project item.</param>
		/// <param name="comment">The parsed comment for this project item.</param>
		public ProjectItem(ProjectInfo projectInfo, Comment comment)
			: base(null, projectInfo.Name, comment)
		{
			this.Info = projectInfo;
			this.ItemType = ItemType.Project;
			this.Documents = new List<DocumentItem>();
			this.AddNamespace(ParserConfig.GlobalNamespace);
		}

		/// <summary>
		/// Gets the list of documents that belong to this project.
		/// </summary>
		public List<DocumentItem> Documents { get; private set; }

		/// <summary>
		/// Gets the project configuration associated with this project item.
		/// </summary>
		public ProjectInfo Info { get; private set; }

		/// <summary>
		/// Gets the name of this item as it will be saved when creating the XML element that represents this item.
		/// </summary>
		/// <value></value>
		public override string XmlItemName
		{
			get
			{
				return FullName;
			}
		}

		/// <summary>
		/// Gets the documentation URL of this code item.
		/// </summary>
		/// <value></value>
		public override string Url
		{
			get
			{
				return this.Info.GetProjectFileName(HtmlPageType.Project, false);
			}
		}

		/// <summary>
		/// Gets the global namespace found in this project.
		/// </summary>
		public NamespaceItem GlobalNamespace
		{
			get
			{
				return this.MemberGroups.Public[ParserConfig.GlobalNamespace] as NamespaceItem;
			}
		}

		/// <summary>
		/// Gets the dictionary of namespaces found in this project.
		/// </summary>
		public Dictionary<string, CodeItem> Namespaces
		{
			get
			{
				return this.PublicMembers;
			}
		}

		/// <summary>
		/// Add the specified <paramref name="document"/> to this project.
		/// </summary>
		/// <param name="document">The document to add.</param>
		/// <returns>A <see cref="DocumentItem"/>, that was added.</returns>
		public DocumentItem AddDocument(DocumentItem document)
		{
			Documents.Add(document);
			return document;
		}

		/// <summary>
		/// Created and adds a namespace to this project, using the specified namespace <paramref name="name"/>.
		/// </summary>
		/// <param name="name">The name of the namespace to add.</param>
		/// <returns>A <see cref="NamespaceItem"/>, created using the specified <paramref namee="name"/>.</returns>
		public NamespaceItem AddNamespace(string name)
		{
			return this.AddNamespace(name, null);
		}

		/// <summary>
		/// Created and adds a namespace to this project, using the specified namespace <paramref name="name"/> and 
		/// <paramref name="comment"/>.
		/// </summary>
		/// <param name="name">The name of the namespace to add.</param>
		/// <param name="comment">The comment string for the namespace item to create and add.</param>
		/// <returns>A <see cref="NamespaceItem"/>, created using the specified <paramref namee="name"/>.</returns>
		public NamespaceItem AddNamespace(string name, Comment comment)
		{
			if (this.MemberGroups[Visibility.Public].ContainsKey(name))
			{
				var ns = this.MemberGroups[Visibility.Public][name];
				if (comment != null)
					ns.Comment = comment;

				return (NamespaceItem) ns;
			}

			var item = new NamespaceItem(this, name, comment);
			this.MemberGroups[Visibility.Public].Add(name, item);
			return item;
		}

		/// <summary>
		/// Generates an XML document containing the index of all members in this project.
		/// </summary>
		/// <param name="outputPrivates">If <c>true</c>, the resulting index file will contain the private members as well.</param>
		/// <returns>An XML document containing the index of all members in this project.</returns>
		public XmlDocument GenerateIndex(bool outputPrivates)
		{
			IEnumerable<CodeItem> parsedMembers = outputPrivates
				? this.MembersRecursive
				: this.MembersRecursive.Where(i => i.Visibility != Visibility.Private);

			Dictionary<string, List<CodeItem>> members = new Dictionary<string, List<CodeItem>>();
			foreach (CodeItem item in parsedMembers)
			{
				string name = item.Name;
				string fullName = item.FullName;

				if (!members.ContainsKey(name))
					members[name] = new List<CodeItem>();

				if (!members[name].Contains(item))
					members[name].Add(item);

				if (name != fullName)
				{
					if (!members.ContainsKey(fullName))
						members[fullName] = new List<CodeItem>();

					if (!members[fullName].Contains(item))
						members[fullName].Add(item);
				}
			}

			XmlDocument result = new XmlDocument();
			XmlElement root = (XmlElement) result.AppendChild(result.CreateElement("project"));
			root.SetAttribute("name", this.Name);

			XmlNode index = root.AppendChild(result.CreateElement("index"));

			foreach (string name in members.Keys.OrderBy(m => m))
			{
				foreach (CodeItem item in members[name])
				{
					XmlElement node = result.CreateElement(item.GetNodeName());
					node.SetAttribute("name", item.Name);
					node.SetAttribute("fullName", item.FullName);
					node.SetAttribute("namePrefix", item.NamePrefix);
					node.SetAttribute("visibility", item.Visibility.ToString().ToLower());
					node.SetAttribute("indexName", name == item.FullName ? item.FullName : item.Name);
					node.SetAttribute("url", item.Url);
					index.AppendChild(node);
				}
			}

			return result;
		}

		/// <summary>
		/// Returns a member with the specified name.
		/// </summary>
		/// <param name="memberName">The name of the member to return.</param>
		/// <returns>The member with the specified name.</returns>
		/// <remarks>The order of searching is:
		/// <list type="number">
		/// <item><term>Prototype</term></item>
		/// <item><term>Static</term></item>
		/// <item><term>Private</term></item>
		/// </list>
		/// </remarks>
		public override CodeItem GetMember(string memberName)
		{
			// if the specified member was not found, let's see if we can find it in global namespace:
			return base.GetMember(memberName) ?? this.GlobalNamespace.GetMember(memberName);
		}

		/// <summary>
		/// Gets the namespace item from this project whose name exactly matches the supplied item name.
		/// </summary>
		/// <param name="name">The name of the namespace.</param>
		/// <returns>
		/// The namespace item from this project whose name exxactly matches the supplied item name.
		/// </returns>
		public NamespaceItem GetNamespace(string name)
		{
			if (String.IsNullOrEmpty(name))
				throw new ArgumentNullException("name");

			if (this.MemberGroups[Visibility.Public].ContainsKey(name))
				return this.MemberGroups[Visibility.Public][name] as NamespaceItem;

			return null;
		}

		/// <summary>
		/// Gets the namespace item from this project whose name is the same as a part of the supplied name's prefix.
		/// </summary>
		/// <param name="typeName">A fully qualified type name.</param>
		/// <returns>
		/// The namespace item from this project whose name is the same as a part of the supplied name's prefix.
		/// </returns>
		public NamespaceItem FindNamespace(string typeName)
		{
			if (String.IsNullOrEmpty(typeName))
				throw new ArgumentNullException("typeName");

			string name = string.Copy(typeName);
			NamespaceItem result = GetNamespace(name);

			while (result == null && name.Contains("."))
			{
				name = Regex.Replace(name, @"(^.*)\..*$", "$1");
				result = GetNamespace(name);
			}

			return result ?? this.GlobalNamespace;
		}

		internal override XmlElement ToXml(XmlDocument document, bool processChildren)
		{
			XmlElement self = base.ToXml(document, processChildren);
			XmlElement documentsNode = document.CreateElement("documents");
			foreach (DocumentItem item in this.Documents)
				documentsNode.AppendChild(item.ToXml(document));

			self.InsertBefore(documentsNode, self.ChildNodes[0]);
			return self;
		}
	}
}
